package dkbta.ontology.elements.pattern;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

import dkbta.ontology.Ontology;
import dkbta.ontology.elements.Element;
import dkbta.ontology.elements.ElementDef;
import dkbta.ontology.elements.ElementType;
import dkbta.ontology.elements.pattern.conditions.PairWiseCondition;
import dkbta.ontology.loading.OntologyException;
import dkbta.processing.ActionTimer;
import dkbta.processing.Patient;

public final class PatternDef extends ElementDef{
	/** The pattern elements the pattern consists of */
	private final PatternElement[] _elements;

	/** The pair-wise conditions on the pattern elements */
	private final PairWiseCondition[] _pwcs;

	/**
	 * Initializes a pattern definition
	 * 
	 * @param name The pattern's name
	 * @param componentType The pattern's component type
	 * @param pwcs The pair-wise conditions on the pattern elements
	 * @param elements The pattern elements the pattern consists of
	 */
	public PatternDef(String name, String componentType,
		Collection<PairWiseCondition> pwcs, Collection<PatternElement> elements){
		super(ElementType.PATTERN, name, componentType, null);
		_elements = new PatternElement[elements.size()];
		for (PatternElement e : elements){
			_elements[e.getOrdinal()] = e;
		}

		_pwcs = rearrangePwcGraph(_elements.length, pwcs);
	}

	@Override
	public void validate(Ontology ontology) throws OntologyException{
		for (PatternElement pe : _elements){
			pe.validate(ontology);
			_prerequisiteElements.add(pe.getElementDef(ontology));
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	protected void compute(Patient p){
		ActionTimer actionTimer = ActionTimer.getInstance();

		int patternActionId = actionTimer.actionStart();

		List<Pattern> patterns = p.getPatterns(_name);
		List<Element>[] elements = new List[_elements.length];

		// Obtaining the valid instances from each pattern element
		for (PatternElement pe : _elements){
			List<Element> validElements = pe.getValidElements(p);
			// If a certain pattern element has no valid instances
			// then no patterns can be created
			if (validElements.isEmpty()){
				return;
			}

			elements[pe.getOrdinal()] = validElements;
		}
		// Initializing the first partial patterns with the instances of the first
		// pattern element
		List<PartialPattern> partialPatterns = new LinkedList<PartialPattern>();
		int initElementOrdinal = 0;
		for (Element e : elements[initElementOrdinal]){
			PartialPattern pp = new PartialPattern(elements.length, initElementOrdinal, e);
			partialPatterns.add(pp);
		}

		// Going over all pair-wise conditions and locating elements that
		// match them
		for (PairWiseCondition pwc : _pwcs){
			int first = pwc.getFirst();
			int second = pwc.getSecond();

			PartialPattern ppTemp = partialPatterns.get(0);
			if (ppTemp.getElement(first) == null){
				if (ppTemp.getElement(second) == null){
					bothElementsMissing(pwc, elements[first], elements[second],
						partialPatterns);
				}else{
					firstElementMissing(pwc, elements[first], partialPatterns);
				}
			}else if (ppTemp.getElement(second) == null){
				secondElementMissing(pwc, elements[second], partialPatterns);
			}else{
				noElementsMissing(pwc, partialPatterns);
			}

			// The partial pattern list is empty it means that none of
			// the partial patterns matched the recent pair-wise condition
			// and so no patterns can be created
			if (partialPatterns.isEmpty()){
				return;
			}
		}

		// Turning each partial pattern into a full pattern
		for (PartialPattern pp : partialPatterns){
			patterns.add(pp.toPattern(_name));
		}

		actionTimer.actionEnd(patternActionId, p.getPatientId(), _name, _type,
			ActionTimer.Action.PATTERN_COMPUTATION);
	}

	private void noElementsMissing(PairWiseCondition pwc,
		List<PartialPattern> partialPatterns){
		ListIterator<PartialPattern> lIter = partialPatterns.listIterator();
		while (lIter.hasNext()){
			PartialPattern pp = lIter.next();
			if (!pwc.checkCondition(pp.getElement(pwc.getFirst()), pp.getElement(pwc
					.getSecond()))){
				lIter.remove();
			}
		}
	}

	private void secondElementMissing(PairWiseCondition pwc,
		List<Element> secondElements, List<PartialPattern> partialPatterns){
		ListIterator<PartialPattern> lIter = partialPatterns.listIterator();
		while (lIter.hasNext()){
			PartialPattern pp = lIter.next();
			lIter.remove();
			for (Element e : secondElements){
				if (pwc.checkCondition(pp.getElement(pwc.getFirst()), e)){
					lIter.add(pp.addElement(pwc.getSecond(), e));
				}
			}
		}
	}

	private void firstElementMissing(PairWiseCondition pwc, List<Element> firstElements,
		List<PartialPattern> partialPatterns){
		ListIterator<PartialPattern> lIter = partialPatterns.listIterator();
		while (lIter.hasNext()){
			PartialPattern pp = lIter.next();
			lIter.remove();
			for (Element e : firstElements){
				if (pwc.checkCondition(e, pp.getElement(pwc.getSecond()))){
					lIter.add(pp.addElement(pwc.getFirst(), e));
				}
			}
		}
	}

	private void bothElementsMissing(PairWiseCondition pwc, List<Element> firstElements,
		List<Element> secondElements, List<PartialPattern> partialPatterns){
		ListIterator<PartialPattern> lIter = partialPatterns.listIterator();
		while (lIter.hasNext()){
			PartialPattern pp = lIter.next();
			lIter.remove();
			for (Element e1 : firstElements){
				for (Element e2 : secondElements){
					if (pwc.checkCondition(e1, e2)){
						lIter.add(pp.addTwoElements(pwc.getFirst(), e1, pwc.getSecond(),
							e2));
					}
				}
			}
		}
	}

	/**
	 * If to view the elements as nodes and the PWCs are bi-directional edges connecting
	 * them, rearranging the Pair-Wise Conditions so that:
	 * <ol>
	 * <li> An entire connected component will be traversed prior to moving to another one
	 * <li> Within the connected component cycles will be traversed as soon as possible
	 * </ol>
	 * 
	 * @param nodeCount The number of nodes/elements
	 * @param pwcEdges The edges/pair-wise conditions
	 * @return The pair-wise conditions according to the aforementioned order
	 */
	private static PairWiseCondition[] rearrangePwcGraph(int nodeCount,
		Collection<PairWiseCondition> pwcEdges){
		// Representing the edges as an adjencency matrix
		PairWiseCondition[][] adjacencyMatrix = new PairWiseCondition[nodeCount][nodeCount];
		for (PairWiseCondition p : pwcEdges){
			adjacencyMatrix[p.getFirst()][p.getSecond()] = p;
			adjacencyMatrix[p.getSecond()][p.getFirst()] = p;
		}

		// A list for the nodes to be placed in the new order
		List<PairWiseCondition> rearrangedNodes = new ArrayList<PairWiseCondition>(
				pwcEdges.size());
		// The nodes already traversed
		boolean[] used = new boolean[nodeCount];

		// Traversing all of the connected components of the PWC graph
		// and rearranging each one
		for (int i = 0; i < nodeCount; i++){
			if (!used[i]){
				used[i] = true;
				rearrangeConnectedComponent(nodeCount, used, i, i, adjacencyMatrix,
					rearrangedNodes);
			}
		}

		return rearrangedNodes.toArray(new PairWiseCondition[rearrangedNodes.size()]);
	}

	/**
	 * Rearranges a single connected component so that cycles will be traversed as soon as
	 * possible
	 */
	private static void rearrangeConnectedComponent(int nodeCount, boolean[] used,
		int current, int from, PairWiseCondition[][] adjencencyMatrix,
		List<PairWiseCondition> rearrangedNodes){
		for (int j = 0; j < nodeCount; j++){
			if (j == from){
				continue;
			}
			if (used[j]){
				PairWiseCondition pairWiseCondition = adjencencyMatrix[current][j];
				if (pairWiseCondition != null){
					rearrangedNodes.add(pairWiseCondition);
				}
			}
		}
		for (int j = 0; j < nodeCount; j++){
			if (j == from || used[j]){
				continue;
			}
			PairWiseCondition p = adjencencyMatrix[current][j];
			if (p == null){
				continue;
			}
			used[j] = true;
			rearrangedNodes.add(adjencencyMatrix[current][j]);
			rearrangeConnectedComponent(nodeCount, used, j, current, adjencencyMatrix,
				rearrangedNodes);
		}
	}

	@Override
	public String completeToString(){
		StringBuilder sb = new StringBuilder(super.completeToString());
		sb.append("Pattern Elements:");
		for (PatternElement pe : _elements){
			sb.append(pe);
		}
		sb.append("\r\nPair-wise Conditions:");
		for (PairWiseCondition pwc : _pwcs){
			sb.append(pwc).append("\r\n");
		}
		return sb.toString();
	}

	@Override
	public Pattern newInstance(long startTime, long endTime, String[] values){
		return new Pattern(_name, startTime, endTime, null);
	}

}
